using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Tools.Common.UnitOfWork.Contracts;
using Tools.Domain.Common.Repositories.Contracts;
using Tools.Domain.Specifications;
using NHibernate;
using NHibernate.Linq;
using Web.App.Infrastructure.NHibernate.Fetching;
using Rhino.Commons;

namespace Web.App.Infrastructure.NHibernate.Repository
{
    public class NHRepository<T> : IRepository<T>
    {
        private IUoWContainer unitOfWorkContainer;
        private readonly IFetchingStrategyFactory fetchingStrategyFactory;
        private ICriteria linqCritera;
        
        private ISession CurrentSession 
        { 
            get
            {
                return unitOfWorkContainer.Current.CurrentSession as ISession;
            }
        
        }

        public NHRepository(IFetchingStrategyFactory fetchingStrategyFactory, IUoWContainer unitOfWorkContainer)
        {
            this.unitOfWorkContainer = unitOfWorkContainer;
            this.fetchingStrategyFactory = fetchingStrategyFactory;
        }

        public virtual void Save(T instance)
        {
            CurrentSession.Save(instance);
        }

        public virtual void Update(T instance)
        {
            CurrentSession.Update(instance);
        }

        public virtual void SaveOrUpdate(T instance)
        {
            CurrentSession.SaveOrUpdate(instance);
        }

        public virtual void SaveOrUpdateCopy(T instance)
        {
            CurrentSession.SaveOrUpdateCopy(instance);
        }

        public virtual void Delete(T instance)
        {
            CurrentSession.Delete(instance);
        }

        public virtual T FindById(object id)
        {
            return CurrentSession.Get<T>(id);
        }



        public virtual IEnumerable<T> FindAll()
        {
            ConfigureFetchingStrategy<T>();
            return CurrentSession
                    .Linq<T>()
                    .Select(x=>x);
        }

        public virtual IQueryable<T> Find(Expression<System.Func<T, bool>> searchTerm)
        {
            ConfigureFetchingStrategy<T>();
            return CurrentSession
                    .Linq<T>()
                    .Where(searchTerm);
        }


        public virtual IQueryable<T> FindBySpecification(ISpecification<T> specification)
        {
            ConfigureFetchingStrategy<T>();
            return CurrentSession
                    .Linq<T>()
                    .Where(
                            specification.AsExpression()
                          );
        }

        public IQueryable<T> FindByQuery(IQuery<T> query)
        {
            return query.Execute(CurrentSession.Linq<T>());
        }

        public virtual IQueryable<R> FindInRole<R>(Expression<System.Func<T, bool>> searchTerm)
        {
            ConfigureFetchingStrategy<T>();
            return CurrentSession
                    .Linq<T>()
                    .Where(searchTerm)
                    .Cast<R>();
        }

        public virtual R FindByIdInRole<R>(object id) 
        {
            return (R) CurrentSession.Load(typeof(T), id); 
        }

        public virtual IEnumerable<R> FindAllInRole<R>()
        {
            ConfigureFetchingStrategy<R>();
            return CurrentSession
                    .Linq<T>()
                    .Select(x => x)
                    .Cast<R>();
        }

        public virtual IQueryable<R> FindBySpecificationInRole<R>(ISpecification<T> specification)
        {
            ConfigureFetchingStrategy<T>();
            return CurrentSession
                    .Linq<T>()
                    .Where(
                            specification.AsExpression()
                          )
                    .Cast<R>();
        }

        public virtual IFutureValue<T> FutureFindById(object id)
        {
            ConfigureFetchingStrategy<T>();
            return new FutureValue<T>(id, FutureValueOptions.NullIfNotFound, CurrentSession);
        }

        public virtual IFutureQueryOf<T> FutureFindAll()
        {
            ConfigureFetchingStrategy<T>();
            CurrentSession.Linq<T>().Select(x=>x);
            CurrentSession.Linq<T>().QueryOptions.RegisterCustomAction(ExtractCriteriaFromLinqExpression);
            return new FutureQueryOf<T>(linqCritera);
        }

        public virtual IFutureQueryOf<T> FutureFind(Expression<System.Func<T, bool>> searchTerm)
        {
            ConfigureFetchingStrategy<T>();
            CurrentSession.Linq<T>().Where(searchTerm);
            CurrentSession.Linq<T>().QueryOptions.RegisterCustomAction(ExtractCriteriaFromLinqExpression);
            return new FutureQueryOf<T>(linqCritera);
        }



        public IFutureQueryOf<T> FutureFindBySpecification(ISpecification<T> specification)
        {
            ConfigureFetchingStrategy<T>();
            CurrentSession.Linq<T>().Where(specification.AsExpression());
            CurrentSession.Linq<T>().QueryOptions.RegisterCustomAction(ExtractCriteriaFromLinqExpression);
            return new FutureQueryOf<T>(linqCritera);
        }


        private void ConfigureFetchingStrategy<R>()
        {
            CurrentSession.Linq<T>().QueryOptions.RegisterCustomAction(AddFetchModeToCriteria<R>);
        }

        private void AddFetchModeToCriteria<R>(ICriteria criteria)
        {
            IEnumerable<IFetchingStrategy<R>> fetchingStrategies =
                fetchingStrategyFactory.CreateFetchingStrategiesFor<R>();

            foreach (var strategy in fetchingStrategies)
            {
                criteria = strategy.ApplyFetchingStrategy(criteria);    
            }
        }

        private void ExtractCriteriaFromLinqExpression(ICriteria critera)
        {
            linqCritera = critera;
        }
    }
}